/**
* personium.io
* Copyright 2014 FUJITSU LIMITED
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fujitsu.dc.core.rs.odata;
import java.io.Reader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.OPTIONS;
import javax.ws.rs.POST;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.UriInfo;
import org.odata4j.core.ODataConstants;
import org.odata4j.core.ODataVersion;
import org.odata4j.core.OEntity;
import org.odata4j.core.OProperty;
import org.odata4j.expression.BoolCommonExpression;
import org.odata4j.expression.EntitySimpleProperty;
import org.odata4j.expression.OrderByExpression;
import org.odata4j.format.FormatWriter;
import org.odata4j.producer.EntitiesResponse;
import org.odata4j.producer.EntityResponse;
import org.odata4j.producer.InlineCount;
import org.odata4j.producer.QueryInfo;
import com.fujitsu.dc.common.utils.DcCoreUtils;
import com.fujitsu.dc.core.DcCoreConfig;
import com.fujitsu.dc.core.DcCoreException;
import com.fujitsu.dc.core.auth.AccessContext;
import com.fujitsu.dc.core.model.ctl.Common;
import com.fujitsu.dc.core.model.ctl.ReceivedMessage;
import com.fujitsu.dc.core.model.ctl.SentMessage;
import com.fujitsu.dc.core.odata.DcFormatWriterFactory;
/**
* ODataのEntitiesリソース( id 指定がなくentitySetが指定されたURL)を扱うJAX-RSリソース.
*/
public final class ODataEntitiesResource extends AbstractODataResource {
private static final int Q_MAX_LENGTH = Common.MAX_Q_VALUE_LENGTH;
ODataResource odataResource;
AccessContext accessContext;
/**
* コンストラクタ.
* @param odataResource 親Resource
* @param entitySetName エンティティセット名
*/
public ODataEntitiesResource(final ODataResource odataResource, final String entitySetName) {
this.odataResource = odataResource;
this.accessContext = this.odataResource.getAccessContext();
setOdataProducer(this.odataResource.getODataProducer());
setEntitySetName(entitySetName);
}
/**
* @param uriInfo UriInfo
* @param accept Acceptヘッダ
* @param format $format パラメタ
* @param callback コールバック
* @param skipToken スキップトークン
* @param q 全文検索パラメタ
* @return JAX-RS Response
*/
@GET
public Response listEntities(
@Context UriInfo uriInfo,
@HeaderParam(HttpHeaders.ACCEPT) final String accept,
@QueryParam("$format") String format,
@QueryParam("$callback") final String callback,
@QueryParam("$skiptoken") final String skipToken,
@QueryParam("q") final String q) {
// アクセス制御
this.odataResource.checkAccessContext(this.accessContext,
this.odataResource.getNecessaryReadPrivilege(getEntitySetName()));
// リクエストの取得をProducerに依頼
EntitiesResponse resp = getEntities(uriInfo, q);
StringWriter sw = new StringWriter();
// $formatとAcceptヘッダの値から出力形式を決定
List<MediaType> acceptableMediaTypes = new ArrayList<MediaType>();
MediaType contentType = decideOutputFormat(accept, format);
acceptableMediaTypes.add(contentType);
FormatWriter<EntitiesResponse> fw = DcFormatWriterFactory.getFormatWriter(EntitiesResponse.class,
acceptableMediaTypes, null, callback);
UriInfo uriInfo2 = DcCoreUtils.createUriInfo(uriInfo, 1);
fw.write(uriInfo2, sw, resp);
String entity = null;
entity = sw.toString();
// 制御コードのエスケープ処理
entity = escapeResponsebody(entity);
// TODO remove this hack, check whether we are Version 2.0 compatible anyway
ODataVersion version = null;
version = ODataVersion.V2;
return Response.ok(entity, fw.getContentType())
.header(ODataConstants.Headers.DATA_SERVICE_VERSION, version.asString).build();
}
/**
* リクエストの取得をProducerに依頼.
* @param queryInfo QueryInfo
* @return レスポンス
*/
EntitiesResponse getEntities(QueryInfo queryInfo) {
EntitiesResponse resp = getOdataProducer().getEntities(getEntitySetName(), queryInfo);
return resp;
}
/**
* リクエストの取得をProducerに依頼.
* @param uriInfo UriInfo
* @param fullTextSearchKeyword String 全文検索を行うキーワード
* @return レスポンス
*/
EntitiesResponse getEntities(UriInfo uriInfo, String fullTextSearchKeyword) {
QueryInfo queryInfo = null;
if (uriInfo != null) {
queryInfo = queryInfo(uriInfo, fullTextSearchKeyword);
}
EntitiesResponse resp = getOdataProducer().getEntities(getEntitySetName(), queryInfo);
return resp;
}
/**
* @param uriInfo UriInfo
* @param accept Acceptヘッダ
* @param format $format パラメタ
* @param reader リクエストボディ
* @return JAX-RS Response
*/
@POST
public Response post(
@Context final UriInfo uriInfo,
@HeaderParam(HttpHeaders.ACCEPT) final String accept,
@DefaultValue(FORMAT_JSON) @QueryParam("$format") final String format,
final Reader reader) {
// メソッド実行可否チェック
checkNotAllowedMethod(uriInfo);
// アクセス制御
this.odataResource.checkAccessContext(this.accessContext,
this.odataResource.getNecessaryWritePrivilege(getEntitySetName()));
UriInfo resUriInfo = DcCoreUtils.createUriInfo(uriInfo, 1);
// Entityの作成を Producerに依頼
EntityResponse res = this.createEntity(reader, this.odataResource);
// 作成結果の評価
OEntity ent = res.getEntity();
// 現状は、ContentTypeはJSON固定
MediaType outputFormat = this.decideOutputFormat(accept, format);
// Entity Responseをレンダー
List<MediaType> contentTypes = new ArrayList<MediaType>();
contentTypes.add(outputFormat);
String key = AbstractODataResource.replaceDummyKeyToNull(ent.getEntityKey().toKeyString());
String responseStr = renderEntityResponse(resUriInfo, res, format, contentTypes);
// 制御コードのエスケープ処理
responseStr = escapeResponsebody(responseStr);
ResponseBuilder rb = getPostResponseBuilder(ent, outputFormat, responseStr, resUriInfo, key);
return rb.build();
}
static QueryInfo queryInfo(UriInfo uriInfo) {
return queryInfo(uriInfo, null);
}
static QueryInfo queryInfo(UriInfo uriInfo, String fullTextSearchKeyword) {
MultivaluedMap<String, String> mm = uriInfo.getQueryParameters(true);
Integer top = QueryParser.parseTopQuery(mm.getFirst("$top"));
Integer skip = QueryParser.parseSkipQuery(mm.getFirst("$skip"));
BoolCommonExpression filter = QueryParser.parseFilterQuery(mm.getFirst("$filter"));
List<EntitySimpleProperty> select = QueryParser.parseSelectQuery(mm.getFirst("$select"));
List<EntitySimpleProperty> expand = QueryParser.parseExpandQuery(mm.getFirst("$expand"));
InlineCount inlineCount = QueryParser.parseInlinecountQuery(mm.getFirst("$inlinecount"));
String skipToken = QueryParser.parseSkipTokenQuery(mm.getFirst("$skiptoken"));
List<OrderByExpression> orderBy = QueryParser.parseOderByQuery(mm.getFirst("$orderby"));
// 全文検索クエリqのバリデート
if (fullTextSearchKeyword != null && (fullTextSearchKeyword.getBytes().length < 1
|| fullTextSearchKeyword.getBytes().length > Q_MAX_LENGTH)) {
throw DcCoreException.OData.QUERY_INVALID_ERROR.params("q", fullTextSearchKeyword);
}
// $expand指定時は$topの最大値が変わるためチェックする
if (expand != null && top != null && top > DcCoreConfig.getTopQueryMaxSizeWithExpand()) {
// Integerでそのまま値を返却すると、カンマが付くため、文字列でエラーメッセージを返却する
throw DcCoreException.OData.QUERY_INVALID_ERROR.params("$top", top.toString());
}
Map<String, String> customOptions = new HashMap<String, String>();
customOptions.put("q", fullTextSearchKeyword);
return new QueryInfo(
inlineCount,
top,
skip,
filter,
orderBy,
skipToken,
customOptions,
expand,
select);
}
/**
* OPTIONSメソッド.
* @return JAX-RS Response
*/
@OPTIONS
public Response options() {
// アクセス制御
this.odataResource.checkAccessContext(this.accessContext,
this.odataResource.getNecessaryReadPrivilege(getEntitySetName()));
return DcCoreUtils.responseBuilderForOptions(
HttpMethod.GET,
HttpMethod.POST
).build();
}
/**
* dc:Format以外のチェック処理.
* @param props プロパティ一覧
*/
@Override
public void validate(List<OProperty<?>> props) {
this.odataResource.validate(props);
}
/**
* メソッド実行可否チェック.
* @param uriInfo リクエストされたリソースパス
*/
private void checkNotAllowedMethod(UriInfo uriInfo) {
// メソッド許可チェック
String[] uriPath = uriInfo.getPath().split("/");
if (ReceivedMessage.EDM_TYPE_NAME.equals(uriPath[(uriPath.length - 1)])
|| SentMessage.EDM_TYPE_NAME.equals(uriPath[(uriPath.length - 1)])) {
throw DcCoreException.Misc.METHOD_NOT_ALLOWED;
}
}
}